package com.cema.manage.common.utils.MS2;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import com.cema.manage.common.utils.StringUtils;
import org.bouncycastle.asn1.*;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Base64;

public class EllipticCurveEncryptionUtill {
    public static final String PUBLIC_KEY = "public_key";
    public static final String PRIVATE_KEY = "private_key";

    public static byte[] encrypt(byte[] publicKey, byte[] data) throws IOException {
        if (publicKey == null || publicKey.length == 0) {
            return null;

        }

        if (data == null || data.length == 0) {
            return null;

        }

        byte[] source = new byte[data.length];

        System.arraycopy(data, 0, source, 0, data.length);

        Cipher cipher = new Cipher();

        SM2 sm2 = SM2.Instance();

        ECPoint userKey = sm2.ecc_curve.decodePoint(publicKey);

        ECPoint c1 = cipher.Init_enc(sm2, userKey);

        cipher.Encrypt(source);

        byte[] c3 = new byte[32];

        cipher.Dofinal(c3);

        DERInteger x = new DERInteger(c1.getX().toBigInteger());

        DERInteger y = new DERInteger(c1.getY().toBigInteger());

        DEROctetString derDig = new DEROctetString(c3);

        DEROctetString derEnc = new DEROctetString(source);

        ASN1EncodableVector v = new ASN1EncodableVector();

        v.add(x);

        v.add(y);

        v.add(derDig);

        v.add(derEnc);

        DERSequence seq = new DERSequence(v);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        DEROutputStream dos = new DEROutputStream(bos);

        dos.writeObject(seq);

        return bos.toByteArray();

    }

    public static byte[] decrypt(byte[] privateKey, byte[] encryptedData) throws IOException {
        if (privateKey == null || privateKey.length == 0) {
            return null;

        }

        if (encryptedData == null || encryptedData.length == 0) {
            return null;

        }

        byte[] enc = new byte[encryptedData.length];

        System.arraycopy(encryptedData, 0, enc, 0, encryptedData.length);

        SM2 sm2 = SM2.Instance();

        BigInteger userD = new BigInteger(1, privateKey);

        ByteArrayInputStream bis = new ByteArrayInputStream(enc);

        ASN1InputStream dis = new ASN1InputStream(bis);

        ASN1Primitive derObj = dis.readObject();

//DERObject derObj = dis.readObject();

        ASN1Sequence asn1 = (ASN1Sequence) derObj;

//DERInteger x = (DERInteger) asn1.getObjectAt(0);

//DERInteger y = (DERInteger) asn1.getObjectAt(1);

        ASN1Integer x = (ASN1Integer) asn1.getObjectAt(0);

        ASN1Integer y = (ASN1Integer) asn1.getObjectAt(1);

        ECPoint c1 = sm2.ecc_curve.createPoint(x.getValue(), y.getValue(), true);

        Cipher cipher = new Cipher();

        cipher.Init_dec(userD, c1);

        DEROctetString data = (DEROctetString) asn1.getObjectAt(3);

        enc = data.getOctets();

        cipher.Decrypt(enc);

        byte[] c3 = new byte[32];

        cipher.Dofinal(c3);

        return enc;

    }

    /**
     * 签名
     *
     * @param userId
     * @param privateKey
     * @param sourceData
     * @return
     * @throws IOException
     */
    public static byte[] sign(byte[] userId, byte[] privateKey, byte[] sourceData) throws IOException {
        if (privateKey == null || privateKey.length == 0) {
            return null;

        }

        if (sourceData == null || sourceData.length == 0) {
            return null;

        }

        SM2 sm2 = SM2.Instance();

        BigInteger userD = new BigInteger(privateKey);

//System.out.println("userD: " + userD.toString(16));

//System.out.println("");

        ECPoint userKey = sm2.ecc_point_g.multiply(userD);

//System.out.println("椭圆曲线点X: " + userKey.getX().toBigInteger().toString(16));

//System.out.println("椭圆曲线点Y: " + userKey.getY().toBigInteger().toString(16));

//System.out.println("");

        byte[] z = sm2.sm2GetZ(userId, userKey);

//System.out.println("SM3摘要Z: " + Util.getHexString(z));

//System.out.println("");

//System.out.println("M: " + Util.getHexString(sourceData));

//System.out.println("");

        SM3Digest sm3 = new SM3Digest();

        sm3.update(z, 0, z.length);

        sm3.update(sourceData, 0, sourceData.length);

        byte[] md = new byte[32];

        sm3.doFinal(md, 0);

//System.out.println("SM3摘要值: " + Util.getHexString(md));

//System.out.println("");

        SM2Result sm2Result = new SM2Result();

        sm2.sm2Sign(md, userD, userKey, sm2Result);

//System.out.println("r: " + sm2Result.r.toString(16));

//System.out.println("s: " + sm2Result.s.toString(16));

//System.out.println("");

        DERInteger d_r = new DERInteger(sm2Result.r);

        DERInteger d_s = new DERInteger(sm2Result.s);

        ASN1EncodableVector v2 = new ASN1EncodableVector();

        v2.add(d_r);

        v2.add(d_s);

        DERSequence sign = new DERSequence(v2);

//DERObject sign = new DERSequence(v2);

//byte[] signdata = sign.getDEREncoded();

        byte[] signdata = sign.getEncoded();

        return signdata;

    }

    @SuppressWarnings("unchecked")
    public static boolean verifySign(byte[] userId, byte[] publicKey, byte[] sourceData, byte[] signData) throws IOException {
        if (publicKey == null || publicKey.length == 0) {
            return false;

        }

        if (sourceData == null || sourceData.length == 0) {
            return false;

        }

        SM2 sm2 = SM2.Instance();

        ECPoint userKey = sm2.ecc_curve.decodePoint(publicKey);

        SM3Digest sm3 = new SM3Digest();

        byte[] z = sm2.sm2GetZ(userId, userKey);

        sm3.update(z, 0, z.length);

        sm3.update(sourceData, 0, sourceData.length);

        byte[] md = new byte[32];

        sm3.doFinal(md, 0);

//System.out.println("SM3摘要值: " + Util.getHexString(md));

//System.out.println("");

        ByteArrayInputStream bis = new ByteArrayInputStream(signData);

        ASN1InputStream dis = new ASN1InputStream(bis);

//DERObject derObj = dis.readObject();

        ASN1Encodable derObj = dis.readObject();

        Enumeration e = ((ASN1Sequence) derObj).getObjects();
//        BigInteger r = e.nextElement().getValue();
//
//        BigInteger s = e.nextElement().getValue();
        ASN1Integer asn1Integer = (ASN1Integer) e.nextElement();
        ASN1Integer asn1Integer2 = (ASN1Integer) e.nextElement();

        SM2Result sm2Result = new SM2Result();

        sm2Result.r = asn1Integer.getValue();

        sm2Result.s = asn1Integer2.getValue();

//System.out.println("r: " + sm2Result.r.toString(16));

//System.out.println("s: " + sm2Result.s.toString(16));

//System.out.println("");

        sm2.sm2Verify(md, userKey, sm2Result.r, sm2Result.s, sm2Result);

        return sm2Result.r.equals(sm2Result.R);

    }

    /**
     * 生成随机秘钥对  （用户）
     *
     * @return
     */
    public static Map<String, String> generateKeyPair() {
        SM2 sm2 = SM2.Instance();
        AsymmetricCipherKeyPair key = null;
        while (true) {
            key = sm2.ecc_key_pair_generator.generateKeyPair();
            if (((ECPrivateKeyParameters) key.getPrivate()).getD().toByteArray().length == 32) {
                break;
            }
        }
        ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();
        ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();
        BigInteger privateKey = ecpriv.getD();
        ECPoint publicKey = ecpub.getQ();
        String pubk = Util.byteToHex(publicKey.getEncoded());
        String prik = Util.byteToHex(privateKey.toByteArray());
        System.out.println("公钥: " + pubk);
        System.out.println("私钥: " + prik);
        Map<String, String> result = new HashMap<>();

        result.put(PUBLIC_KEY, pubk);
        result.put(PRIVATE_KEY, prik);

        return result;
    }

    public static void main(String[] args) throws Exception {
        String plainText = "151ADCDEF02321DEF123DE" + "alibabb" + "hjj" + "yyyy";

        //1.源数据数组
        byte[] sourceData = plainText.getBytes();
        // 国密规范测试私钥
        String prik = "128B2FA8BD433C6C068C8D803DFF79792A519A55171B1B650C23661D15897263";
        //2.私钥先转十六进制，然后进行Base64编码
        String prikS = new String(Base64.encode(Util.hexToByte(prik)));
        System.out.println("转换后的私钥：prikS====" + prikS);

        //System.out.println("");
        // 国密规范测试用户ID

        String userId = "ALICE123@YAHOO.COM";
        //获取userId十六进制字符串
        System.out.println("十六进制userId: " + Util.getHexString(userId.getBytes()));

        //System.out.println("");
        //System.out.println("签名: ");
        //3.用userId和私钥，对明文数据签名(userid、prik、sourceData)

        byte[] c = EllipticCurveEncryptionUtill.sign(userId.getBytes(), Base64.decode(prikS.getBytes()), sourceData);

        System.out.println("SM2签名后值====" + Util.getHexString(c));

        //System.out.println("");
        // 国密规范测试公钥

        String pubk = "040AE4C7798AA0F119471BEE11825BE46202BB79E2A5844495E97C04FF4DF2548A7C0240F88F1CD4E16352A73C17B7F16F07353E53A176D684A9FE0C6BB798E857";

        String pubkS = new String(Base64.encode(Util.hexToByte(pubk)));

        System.out.println("转换后的公钥pubkS====" + pubkS);

        //System.out.println("");
        //System.out.println("验签: ");

        //4.用公钥进行验签(userId、pubk、sourceData、签名数据c)

        boolean vs = EllipticCurveEncryptionUtill.verifySign(userId.getBytes(), Base64.decode(pubkS.getBytes()), sourceData, c);

        System.out.println("验签结果: " + vs);

        //System.out.println("");
        //System.out.println("加密: ");
        //5.SM2加密算法

        byte[] cipherText = EllipticCurveEncryptionUtill.encrypt(Base64.decode(pubkS.getBytes()), sourceData);
        String str = new String(Base64.encode(cipherText));
        System.out.println("SM2加密后结果===" + str);
        String str1 = toHexString(cipherText);
        System.out.println(str1);
        byte[]  str2 =  toByteArray(str1);
        System.out.println(str2);
        //System.out.println("解密: ");

        //6.SM2解密算法

        plainText = new String(EllipticCurveEncryptionUtill.decrypt(Base64.decode(prikS.getBytes()), str2));

        System.out.println("解密后获取的结果===" + plainText);

    }

    public static byte[] toByteArray(String hexString) {
        if (StringUtils.isEmpty(hexString))
            throw new IllegalArgumentException("this hexString must not be empty");

        hexString = hexString.toLowerCase();
        final byte[] byteArray = new byte[hexString.length() / 2];
        int k = 0;
        for (int i = 0; i < byteArray.length; i++) {//因为是16进制，最多只会占用4位，转换成字节需要两个16进制的字符，高位在先
            byte high = (byte) (Character.digit(hexString.charAt(k), 16) & 0xff);
            byte low = (byte) (Character.digit(hexString.charAt(k + 1), 16) & 0xff);
            byteArray[i] = (byte) (high << 4 | low);
            k += 2;
        }
        return byteArray;
    }

    /**
     * 字节数组转成16进制表示格式的字符串
     *
     * @param byteArray 需要转换的字节数组
     * @return 16进制表示格式的字符串
     **/
    public static String toHexString(byte[] byteArray) {
        if (byteArray == null || byteArray.length < 1)
            throw new IllegalArgumentException("this byteArray must not be null or empty");

        final StringBuilder hexString = new StringBuilder();
        for (int i = 0; i < byteArray.length; i++) {
            if ((byteArray[i] & 0xff) < 0x10)//0~F前面不零
                hexString.append("0");
            hexString.append(Integer.toHexString(0xFF & byteArray[i]));
        }
        return hexString.toString().toLowerCase();
    }
}
